package net.ncraft.mc.ezframework.core.ezbatis;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class MapperProxy implements InvocationHandler {

    private final Environment environment;

    public MapperProxy(Environment environment) {
        this.environment = environment;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) {

        Connection connection = null;

        try {
            connection = environment.getDataSource().getConnection();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }

        if (connection == null) {
            throw new RuntimeException("connection is null");
        }

        if (args == null) {
            args = new Object[]{};
        }

        try {
            if (method.isAnnotationPresent(Select.class)) {
                Select select = method.getAnnotation(Select.class);
                String sqlStr = select.value();
                Map<Integer, String> sqlList = StringUtil.replaceBrackets(sqlStr, args);
                PreparedStatement preparedStatement = connection.prepareStatement(sqlList.get(0));

                for (int i = 0; i < args.length; i++) {
                    if (args[i] instanceof Object) {
                        Class<?> objClass = args[i].getClass();
                        for (Field declaredField : objClass.getDeclaredFields()) {
                            declaredField.setAccessible(true);
                            Object o = declaredField.get(args[i]);
                            String fieldName = declaredField.getName();
                            sqlList.forEach((k, v) -> {
                                if (fieldName.equals(v)) {
                                    try {
                                        preparedStatement.setObject(k, o);
                                    } catch (SQLException e) {
                                        throw new RuntimeException(e);
                                    }
                                }
                            });
                        }
                    }else {
                        int finalI = i;

                        for (Integer integer : sqlList.keySet()) {
                            if (integer==0) continue;
                            preparedStatement.setObject(integer, args[finalI]);
                        }
                    }

                }

                ResultSet resultSet = preparedStatement.executeQuery();
                Class<?> returnType = method.getReturnType();

                if (List.class.isAssignableFrom(returnType)) {
                    Type genericReturnType = method.getGenericReturnType();
                    String genericClassStr = StringUtil.getGenericClass(genericReturnType.toString());

                    Class<?> genericClass = Class.forName(genericClassStr);
                    List<Object> list = new ArrayList<>();
                    while (resultSet.next()) {
                        Object o = genericClass.newInstance();

                        for (Field declaredField : genericClass.getDeclaredFields()) {
                            Object object = resultSet.getObject(declaredField.getName());
                            declaredField.setAccessible(true);
                            declaredField.set(o, object);
                        }
                        list.add(o);
                    }
                    return list;
                }

                Object o = returnType.newInstance();

                while (resultSet.next()) {
                    for (Field declaredField : returnType.getDeclaredFields()) {
                        Object object = resultSet.getObject(declaredField.getName());
                        declaredField.setAccessible(true);
                        declaredField.set(o, object);
                    }
                }
                return o;
            }
            if (method.isAnnotationPresent(Insert.class)) {
                Insert insert = method.getAnnotation(Insert.class);
                String sqlStr = insert.value();
                Map<Integer, String> sqlList = StringUtil.replaceBrackets(sqlStr, args);
                PreparedStatement preparedStatement = connection.prepareStatement(sqlList.get(0));

                for (int i = 0; i < args.length; i++) {
                    if (args[i] instanceof Object) {
                        Class<?> objClass = args[i].getClass();
                        for (Field declaredField : objClass.getDeclaredFields()) {
                            declaredField.setAccessible(true);
                            Object o = declaredField.get(args[i]);
                            String fieldName = declaredField.getName();
                            sqlList.forEach((k, v) -> {
                                if (fieldName.equals(v)) {
                                    try {
                                        preparedStatement.setObject(k, o);
                                    } catch (SQLException e) {
                                        throw new RuntimeException(e);
                                    }
                                }
                            });
                        }
                    }else {
                        int finalI = i;

                        for (Integer integer : sqlList.keySet()) {
                            if (integer==0) continue;
                            preparedStatement.setObject(integer, args[finalI]);
                        }
                    }

                }
                return preparedStatement.execute();
            }
            if (method.isAnnotationPresent(Update.class)) {
                Update update = method.getAnnotation(Update.class);
                String sqlStr = update.value();
                Map<Integer, String> sqlList = StringUtil.replaceBrackets(sqlStr, args);
                PreparedStatement preparedStatement = connection.prepareStatement(sqlList.get(0));

                for (int i = 0; i < args.length; i++) {
                    if (args[i] instanceof Object) {
                        Class<?> objClass = args[i].getClass();
                        for (Field declaredField : objClass.getDeclaredFields()) {
                            declaredField.setAccessible(true);
                            Object o = declaredField.get(args[i]);
                            String fieldName = declaredField.getName();
                            sqlList.forEach((k, v) -> {
                                if (fieldName.equals(v)) {
                                    try {
                                        preparedStatement.setObject(k, o);
                                    } catch (SQLException e) {
                                        throw new RuntimeException(e);
                                    }
                                }
                            });
                        }
                    }else {
                        int finalI = i;

                        for (Integer integer : sqlList.keySet()) {
                            if (integer==0) continue;
                            preparedStatement.setObject(integer, args[finalI]);
                        }
                    }

                }
                return preparedStatement.execute();

            }
            if (method.isAnnotationPresent(Delete.class)) {
                Delete delete = method.getAnnotation(Delete.class);
                String sqlStr = delete.value();
                Map<Integer, String> sqlList = StringUtil.replaceBrackets(sqlStr, args);
                PreparedStatement preparedStatement = connection.prepareStatement(sqlList.get(0));

                for (int i = 0; i < args.length; i++) {
                    if (args[i] instanceof Object) {
                        Class<?> objClass = args[i].getClass();
                        for (Field declaredField : objClass.getDeclaredFields()) {
                            declaredField.setAccessible(true);
                            Object o = declaredField.get(args[i]);
                            String fieldName = declaredField.getName();
                            sqlList.forEach((k, v) -> {
                                if (fieldName.equals(v)) {
                                    try {
                                        preparedStatement.setObject(k, o);
                                    } catch (SQLException e) {
                                        throw new RuntimeException(e);
                                    }
                                }
                            });
                        }
                    }else {
                        int finalI = i;

                        for (Integer integer : sqlList.keySet()) {
                            if (integer==0) continue;
                            preparedStatement.setObject(integer, args[finalI]);
                        }
                    }

                }
                return preparedStatement.execute();
            }
        }catch (SQLException | InstantiationException | IllegalAccessException | ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        return null;
    }
}
